package com.nulldev.util.web.YouTubeExtractor.related;

import static com.nulldev.util.web.YouTubeExtractor.util.YouTubeExtractorUtil.getYTString;
import static com.nulldev.util.web.YouTubeExtractor.util.YouTubeExtractorUtil.getYTStringF;

import java.util.List;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.MathUtil.TimeUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.languages.json.uniapi.UniJsonIterableValue;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.YouTubeExtractor.extractors.util.SharedExtractorUtil;
import com.nulldev.util.web.YouTubeExtractor.related.VideoInfo.VideoInfoType;

public class RelatedParser {
	private static final Logger log = LoggerFactory.getLogger();

	/*
	 * As of 1.5-indev-120 (17.03.2020) the legacy parser has been removed from
	 * nTube. (It's not like it would work since March 2020 and the removal of the
	 * legacy UI)
	 */

	/**
	 * Enabled as of YTE 2.2/nTube 1.5.4
	 */
	private static final boolean ENABLE_SHOWS = Arguments.notArguments("--RelatedParser.disableShows", "--YTE.disableShows");

	private static void processRichItemObject(final RelatedInfo ri, final List<VideoInfo> videoInfos, final UniJsonValue s) {
		if (s.contains("videoRenderer")) {
			final UniJsonValue videoRenderer = s.get("videoRenderer");
			long length;
			try {
				length = MathUtil.TimeToSeconds.parseTimeStringToSeconds(getYTString(videoRenderer.get("lengthText")));
			} catch (Exception e) {
				length = -1;
			}
			final String videoID = videoRenderer.getString("videoId");
			String author = getYTString(videoRenderer.get("longBylineText"));
			if (author == null) {
				if (videoRenderer.get("longBylineText").hasValue("simpleText")) {
					author = videoRenderer.get("longBylineText").getString("simpleText");
					if (author == null) {
						log.warn("Author is null!");
						log.warn(videoRenderer.toString());
					}
				} else {
					log.warn("Author is null!");
					log.warn(videoRenderer.toString());
				}
			}
			String animatedThumbnail = null;
			if (videoRenderer.hasValue("richThumbnail")) {
				if (videoRenderer.get("richThumbnail").contains("movingThumbnailRenderer")) {
					if (videoRenderer.mget("richThumbnail.movingThumbnailRenderer").contains("movingThumbnailDetails")) {
						animatedThumbnail = videoRenderer.get("richThumbnail").get("movingThumbnailRenderer").get("movingThumbnailDetails").get("thumbnails")
								.asArray().first().getString("url");
					}
				} else if (videoRenderer.get("richThumbnail").contains("movingThumbnailDetails")) {
					animatedThumbnail = videoRenderer.get("richThumbnail").get("movingThumbnailDetails").get("thumbnails").asArray().get(0).getString("url");
				}
			}
			String viewsString = "";
			if (videoRenderer.hasValue("viewCountText")) {
				viewsString = getYTStringF(videoRenderer.get("viewCountText"), "");
			}
			videoInfos.add(new VideoInfo(getYTString(videoRenderer.get("title")), videoID, author, TimeUtil.formatSecondsL(length), viewsString)
					.setThumbnailID(videoID).setDuration(length).setAnimatedURL(animatedThumbnail));
		} else if (s.contains("radioRenderer")) {
			final UniJsonValue radioRenderer = s.get("radioRenderer");
			final String playlist_title = getYTString(radioRenderer.get("title"));
			final String videoID = radioRenderer.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
			final String playlistID = radioRenderer.get("navigationEndpoint").get("watchEndpoint").getString("playlistId");
			final String link = videoID + "&list=" + playlistID;
			final String author = getYTString(radioRenderer.get("longBylineText"));
			videoInfos.add(new VideoInfo(playlist_title, link, author, TimeUtil.formatSecondsL(-1), "").setType(VideoInfoType.PLAYLIST).setThumbnailID(videoID)
					.setDuration(-1).setPlaylist(true));
		} else if (s.contains("playlistRenderer")) {
			final UniJsonValue playlistRenderer = s.get("playlistRenderer");
			final String playlist_title = getYTString(playlistRenderer.get("title"));
			final String videoID = playlistRenderer.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
			final String playlistID = playlistRenderer.get("navigationEndpoint").get("watchEndpoint").getString("playlistId");
			final String link = videoID + "&list=" + playlistID;
			final String author = getYTString(playlistRenderer.get("longBylineText"));
			videoInfos.add(new VideoInfo(playlist_title, link, author, TimeUtil.formatSecondsL(-1), "").setType(VideoInfoType.PLAYLIST).setThumbnailID(videoID)
					.setDuration(-1).setPlaylist(true));
		} else {
			log.warn("[processRichItemObject] Unknown rich item object: " + s);
		}
	}

	private static void processRelatedArray(final RelatedInfo ri, final List<VideoInfo> videoInfos, final UniJsonIterableValue s) {
		for (final UniJsonValue v : s) {
			try {
				if (v.hasValue("compactAutoplayRenderer")) {
					final UniJsonValue m = v.get("compactAutoplayRenderer").get("contents").asArray().get(0).get("compactVideoRenderer");
					if (m == null || m.isNull()) {
						final UniJsonValue rm = v.get("compactAutoplayRenderer").get("contents").first().get("compactRadioRenderer");
						final String videoID = rm.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
						final String playlistID = rm.get("navigationEndpoint").get("watchEndpoint").getString("playlistId");
						// TODO: Add playlists.
						log.debug("playlist: " + rm);
						videoInfos.add(new VideoInfo(getYTString(m.get("title")), videoID + "&playlist=" + playlistID,
								m.get("shortBylineText").getString("simpleText"), "-1", "-1").setThumbnailID(videoID).setDuration(-3).setUpnext(true));
						continue;
					} else {
						long length;
						try {
							length = MathUtil.TimeToSeconds.parseTimeStringToSeconds(getYTString(m.get("lengthText")));
						} catch (Exception e) {
							length = -1;
						}
						String videoID = m.getString("videoId", null);
						if (videoID == null) {
							videoID = m.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
							// FIXME: Should we do playlists as well?
							// playlist:
							// m.get("navigationEndpoint").get("watchEndpoint").getString("playlistId");
						}
						String animatedThumbnail = null;
						if (m.hasValue("richThumbnail")) {
							if (m.get("richThumbnail").contains("movingThumbnailRenderer")) {
								if (m.mget("richThumbnail.movingThumbnailRenderer").contains("movingThumbnailDetails")) {
									animatedThumbnail = m.get("richThumbnail").get("movingThumbnailRenderer").get("movingThumbnailDetails").get("thumbnails")
											.asArray().first().getString("url");
								}
							} else if (m.get("richThumbnail").contains("movingThumbnailDetails")) {
								animatedThumbnail = m.get("richThumbnail").get("movingThumbnailDetails").get("thumbnails").asArray().get(0).getString("url");
							}
						}
						String author = getYTString(m.get("longBylineText"));
						if (author == null) {
							if (m.get("longBylineText").hasValue("simpleText")) {
								author = m.get("longBylineText").getString("simpleText");
								if (author == null) {
									log.warn("Author is null!");
									log.warn(m.toString());
								}
							} else {
								author = m.get("longBylineText").get("runs").asArray().get(0).getString("text");
							}
						}
						String viewsString = "";
						if (m.hasValue("viewCountText")) {
							viewsString = getYTStringF(m.get("viewCountText"), "");
						}
						videoInfos.add(new VideoInfo(getYTString(m.get("title")), videoID, author, TimeUtil.formatSecondsL(length), viewsString)
								.setThumbnailID(videoID).setDuration(length).setUpnext(true).setAnimatedURL(animatedThumbnail));
					}
				} else if (v.hasValue("compactRadioRenderer")) {
					final UniJsonValue m = v.get("compactRadioRenderer");
					final String videoID = m.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
					final String link = videoID + "&list=" + m.getString("playlistId");
					String author = getYTString(m.safe("longBylineText"));
					if (author == null) {
						if (m.get("longBylineText").hasValue("simpleText")) {
							author = m.get("longBylineText").getString("simpleText");
							if (author == null) {
								log.warn("Author is null!");
								log.warn(m.toString());
							}
						} else {
							author = m.get("longBylineText").get("runs").asArray().get(0).getString("text");
						}
					}
					String viewsString = "";
					if (m.hasValue("viewCountText")) {
						viewsString = getYTStringF(m.get("viewCountText"), "");
					}
					String animatedThumbnail = null;
					if (m.hasValue("richThumbnail")) {
						if (m.get("richThumbnail").contains("movingThumbnailRenderer")) {
							if (m.mget("richThumbnail.movingThumbnailRenderer").contains("movingThumbnailDetails")) {
								animatedThumbnail = m.get("richThumbnail").get("movingThumbnailRenderer").get("movingThumbnailDetails").get("thumbnails")
										.asArray().first().getString("url");
							}
						} else if (m.get("richThumbnail").contains("movingThumbnailDetails")) {
							animatedThumbnail = m.get("richThumbnail").get("movingThumbnailDetails").get("thumbnails").asArray().get(0).getString("url");
						}
					}
					videoInfos.add(new VideoInfo(getYTString(m.get("title")), link, author, TimeUtil.formatSecondsL(-1), viewsString)
							.setType(VideoInfoType.PLAYLIST).setThumbnailID(videoID).setDuration(-1).setPlaylist(true).setAnimatedURL(animatedThumbnail));
				} else if (v.hasValue("compactPlaylistRenderer")) {
					final UniJsonValue m = v.get("compactPlaylistRenderer");
					final String videoID = m.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
					final String link = videoID + "&list=" + m.getString("playlistId");
					String author = null;
					try {
						if (m.contains("shortBylineText") && m.get("shortBylineText").contains("runs")) {
							author = m.get("shortBylineText").get("runs").first().getString("text");
						} else {
							if (m.contains("longBylineText") && m.get("longBylineText").contains("simpleText")) {
								author = m.get("longBylineText").getString("simpleText");
							}
						}
					} catch (Exception e) {
						log.warn("Failed to get author in PLAYLIST.");
					}
					if (author == null) {
						try {
							if (m.get("longBylineText").hasValue("simpleText")) {
								author = m.get("longBylineText").getString("simpleText");
								if (author == null) {
									log.warn("Author is null!");
									log.warn(m.toString());
								}
							} else {
								author = m.get("longBylineText").get("runs").asArray().get(0).getString("text");
							}
						} catch (Exception e) {
							log.error("Failed to get author!", e);
							author = "N/A";
						}
					}
					videoInfos.add(new VideoInfo(getYTString(m.get("title")), link, author, TimeUtil.formatSecondsL(-1), "").setType(VideoInfoType.PLAYLIST)
							.setThumbnailID(videoID).setDuration(-1).setPlaylist(true));
				} else if (v.hasValue("compactVideoRenderer")) {
					final UniJsonValue m = v.get("compactVideoRenderer");
					long length;
					try {
						length = MathUtil.TimeToSeconds.parseTimeStringToSeconds(getYTString(m.get("lengthText")));
					} catch (Exception e) {
						length = -1;
					}
					final String videoID = m.getString("videoId");
					String author = m.get("longBylineText").get("runs").asArray().get(0).getString("text");
					if (author == null) {
						if (m.get("longBylineText").hasValue("simpleText")) {
							author = m.get("longBylineText").getString("simpleText");
							if (author == null) {
								log.warn("Author is null!");
								log.warn(m.toString());
							}
						} else {
							log.warn("Author is null!");
							log.warn(m.toString());
						}
					}
					String animatedThumbnail = null;
					if (m.hasValue("richThumbnail")) {
						if (m.get("richThumbnail").contains("movingThumbnailRenderer")) {
							if (m.mget("richThumbnail.movingThumbnailRenderer").contains("movingThumbnailDetails")) {
								animatedThumbnail = m.get("richThumbnail").get("movingThumbnailRenderer").get("movingThumbnailDetails").get("thumbnails")
										.asArray().first().getString("url");
							}
						} else if (m.get("richThumbnail").contains("movingThumbnailDetails")) {
							animatedThumbnail = m.get("richThumbnail").get("movingThumbnailDetails").get("thumbnails").asArray().get(0).getString("url");
						}
					}
					String viewsString = "";
					if (m.hasValue("viewCountText")) {
						viewsString = getYTStringF(m.get("viewCountText"), "");
					}
					videoInfos.add(new VideoInfo(getYTString(m.get("title")), videoID, author, TimeUtil.formatSecondsL(length), viewsString)
							.setThumbnailID(videoID).setDuration(length).setAnimatedURL(animatedThumbnail));
				} else if (v.hasValue("compactPromotedVideoRenderer")) {
					continue; // rejected
				} else if (v.hasValue("compactMovieRenderer")) {
					final UniJsonValue m = v.get("compactMovieRenderer");
					long length;
					try {
						length = MathUtil.TimeToSeconds.parseTimeStringToSeconds(getYTString(m.get("lengthText")));
					} catch (Exception e) {
						length = -1;
					}
					final String videoID = m.getString("videoId");
					String author = m.get("shortBylineText").get("runs").asArray().get(0).getString("text");
					if (author == null) {
						if (m.get("longBylineText").hasValue("simpleText")) {
							author = m.get("longBylineText").getString("simpleText");
							if (author == null) {
								log.warn("Author is null!");
								log.warn(m.toString());
							}
						} else {
							log.warn("Author is null!");
							log.warn(m.toString());
						}
					}
					videoInfos.add(new VideoInfo(getYTString(m.get("title")), videoID, author, TimeUtil.formatSecondsL(length), "").setThumbnailID(videoID)
							.setDuration(length));
				} else if (v.hasValue("continuationItemRenderer")) {
					final UniJsonValue m = v.get("continuationItemRenderer");
					if (m.contains("continuationEndpoint")) {
						ri.setContinuation(m.get("continuationEndpoint").get("continuationCommand").getString("token"));
					} else {
						log.warn("Unknown continuationItemRenderer: " + m);
					}
				} else if (v.hasValue("gridVideoRenderer")) {
					final UniJsonValue m = v.get("gridVideoRenderer");
					long length;
					try {
						length = MathUtil.TimeToSeconds.parseTimeStringToSeconds(getYTString(m.get("lengthText")));
					} catch (Exception e) {
						length = -1;
					}
					final String videoID = m.getString("videoId");
					String author = getYTString(m.get("longBylineText"));
					if (author == null) {
						if (m.get("longBylineText").hasValue("simpleText")) {
							author = m.get("longBylineText").getString("simpleText");
							if (author == null) {
								log.warn("Author is null!");
								log.warn(m.toString());
							}
						} else {
							log.warn("Author is null!");
							log.warn(m.toString());
						}
					}
					String animatedThumbnail = null;
					if (m.hasValue("richThumbnail")) {
						if (m.get("richThumbnail").contains("movingThumbnailRenderer")) {
							if (m.mget("richThumbnail.movingThumbnailRenderer").contains("movingThumbnailDetails")) {
								animatedThumbnail = m.get("richThumbnail").get("movingThumbnailRenderer").get("movingThumbnailDetails").get("thumbnails")
										.asArray().first().getString("url");
							}
						} else if (m.get("richThumbnail").contains("movingThumbnailDetails")) {
							animatedThumbnail = m.get("richThumbnail").get("movingThumbnailDetails").get("thumbnails").asArray().get(0).getString("url");
						}
					}
					String viewsString = "";
					if (m.hasValue("viewCountText")) {
						viewsString = getYTStringF(m.get("viewCountText"), "");
					}
					videoInfos.add(new VideoInfo(getYTString(m.get("title")), videoID, author, TimeUtil.formatSecondsL(length), viewsString)
							.setThumbnailID(videoID).setDuration(length).setAnimatedURL(animatedThumbnail));
				} else if (v.hasValue("gridRadioRenderer")) {
					final UniJsonValue m = v.get("gridRadioRenderer");
					final String videoID = m.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
					final String link = videoID + "&list=" + m.getString("playlistId");
					String author = getYTString(m.get("longBylineText"));
					if (author == null) {
						if (m.get("longBylineText").hasValue("simpleText")) {
							author = m.get("longBylineText").getString("simpleText");
							if (author == null) {
								log.warn("Author is null!");
								log.warn(m.toString());
							}
						} else {
							author = m.get("longBylineText").get("runs").asArray().get(0).getString("text");
						}
					}
					String viewsString = "";
					if (m.hasValue("viewCountText")) {
						viewsString = getYTStringF(m.get("viewCountText"), "");
					}
					String animatedThumbnail = null;
					if (m.hasValue("richThumbnail")) {
						if (m.get("richThumbnail").contains("movingThumbnailRenderer")) {
							if (m.mget("richThumbnail.movingThumbnailRenderer").contains("movingThumbnailDetails")) {
								animatedThumbnail = m.get("richThumbnail").get("movingThumbnailRenderer").get("movingThumbnailDetails").get("thumbnails")
										.asArray().first().getString("url");
							}
						} else if (m.get("richThumbnail").contains("movingThumbnailDetails")) {
							animatedThumbnail = m.get("richThumbnail").get("movingThumbnailDetails").get("thumbnails").asArray().get(0).getString("url");
						}
					}
					videoInfos.add(new VideoInfo(getYTString(m.get("title")), link, author, TimeUtil.formatSecondsL(-1), viewsString)
							.setType(VideoInfoType.PLAYLIST).setThumbnailID(videoID).setDuration(-1).setPlaylist(true).setAnimatedURL(animatedThumbnail));
				} else if (v.hasValue("gridPlaylistRenderer")) {
					final UniJsonValue m = v.get("gridPlaylistRenderer");
					final String videoID = m.get("navigationEndpoint").get("watchEndpoint").getString("videoId");
					final String link = videoID + "&list=" + m.getString("playlistId");
					String author = getYTString(m.get("longBylineText"));
					if (author == null) {
						try {
							if (m.get("longBylineText").hasValue("simpleText")) {
								author = m.get("longBylineText").getString("simpleText");
								if (author == null) {
									log.warn("Author is null!");
									log.warn(m.toString());
								}
							} else {
								author = m.get("longBylineText").get("runs").asArray().get(0).getString("text");
							}
						} catch (Exception e) {
							log.error("Failed to get author!", e);
							author = "N/A";
						}
					}
					videoInfos.add(new VideoInfo(getYTString(m.get("title")), link, author, TimeUtil.formatSecondsL(-1), "").setType(VideoInfoType.PLAYLIST)
							.setThumbnailID(videoID).setDuration(-1).setPlaylist(true));
				} else if (v.contains("gridShowRenderer") && ENABLE_SHOWS) {
					final UniJsonValue m = v.get("gridShowRenderer");
					final String title = getYTString(m.get("title"));
					final String browseId = m.get("navigationEndpoint").get("browseEndpoint").getString("browseId");
					final String link = browseId;
					final String author = getYTString(m.get("longBylineText"));
					final String videoID = StringsUtil.substringBetweenO(
							m.get("thumbnailRenderer").get("showCustomThumbnailRenderer").get("thumbnail").get("thumbnails").first().getString("url"), "/vi/",
							"/");
					videoInfos.add(new VideoInfo(title, link, author, TimeUtil.formatSecondsL(-1), "").setType(VideoInfoType.SHOW).setThumbnailID(videoID)
							.setDuration(-1));
				} else if (v.contains("richItemRenderer")) {
					final UniJsonValue richItemRenderer = v.get("richItemRenderer");
					final UniJsonValue content = richItemRenderer.get("content");
					processRichItemObject(ri, videoInfos, content);
				} else if (v.contains("promotedSparklesWebRenderer")) {
					log.dwarn("Dropped advert object: promotedSparklesWebRenderer");
				} else if (v.contains("relatedChipCloudRenderer")) {
					// THE NEW UI IS SHIT. ~ 2022
					log.dwarn("Dropped unsupported object: relatedChipCloudRenderer");
				} else if (v.contains("adSlotRenderer")) {
					log.dwarn("Dropped unsupported object: adSlotRenderer");
				} else if (v.hasValue("itemSectionRenderer")) {
					final UniJsonValue pre = v.get("itemSectionRenderer");
					if (pre.contains("contents") && pre.get("contents").isArray()) {
						final UniJsonValue contents = pre.get("contents");
						processRelatedArray(ri, videoInfos, contents.asArray());
					} else {
						log.warn("Unknown itemSectionRenderer: " + pre);
					}
				} else if (v.contains("richGridRenderer")) {
					// The new UI IS STILL SHIT ~ 2023
					final UniJsonValue richGridRenderer = v.get("richGridRenderer");
					if (richGridRenderer.contains("contents") && richGridRenderer.get("contents").isArray()) {
						final UniJsonValue contents = richGridRenderer.get("contents");
						processRelatedArray(ri, videoInfos, contents.asArray());
					} else {
						log.warn("Unknown richGridRenderer: " + richGridRenderer);
					}
				} else if (v.contains("reelShelfRenderer")) {
					log.dwarn("Dropped unimplemented object: reelShelfRenderer");
				} else {
					log.warn("Unknown object: " + v);
				}
			} catch (Exception e) {
				log.error(v.toString());
				log.error("[processRelatedArray] Failed to parse related entry!", e);
			}
		}
	}

	public static class RelatedInfo {
		private final List<VideoInfo> infos;
		private String continuation;

		public RelatedInfo(final List<VideoInfo> infos) {
			this.infos = Variables.requireNonNullObject(infos, "infos");
		}

		public List<VideoInfo> related() {
			return this.infos;
		}

		public RelatedInfo setContinuation(final String cont) {
			this.continuation = cont;
			return this;
		}

		public boolean hasContinuation() {
			return this.continuation != null && !this.continuation.isEmpty();
		}

		public String getContinuation() {
			return this.continuation;
		}

		public int size() {
			return this.infos.size();
		}
	}

	public static RelatedInfo parse(final String data, final String initialVideoID) {
		final List<VideoInfo> videoInfos = FastLists.list(20);
		final RelatedInfo ri = new RelatedInfo(videoInfos);
		if (data == null || data.isEmpty())
			return ri;
		if (data.equalsIgnoreCase("TODO_IMPLEMENT_MAPPINGS_FOR_RELATED_SOURCES")) {
			return ri;
		}
		log.debug("Parsing related... [method=nTubeNext]");
		final Executable<UniJsonValue> ytInitialData = SharedExtractorUtil.getYTInitialData(data);
		UniJsonValue o = ytInitialData.waitTillCompletion();
		if (o == null) {
			log.error("ytInitialData returned null!");
			return ri;
		}
		if (o.isArray()) {
			o = o.arrayGetContains("response").get("response");
		}
		if (!o.hasValue("contents")) {
			log.warn("Failed to find 'contents'!");
			o.println();
			return ri;
		}
		final UniJsonValue results = o.get("contents").getEither("twoColumnWatchNextResults", "singleColumnWatchNextResults");
		List<UniJsonValue> primary;
		try {
			primary = results.get("results").get("results").get("contents").asArray().getAllInListEither("itemSectionRenderer", "shelfRenderer");
		} catch (Exception e) {
			primary = results.get("secondaryResults").get("secondaryResults").get("results").asArray().getAllInListEither("itemSectionRenderer",
					"shelfRenderer");
		}
		boolean USE_SECONDARY_RESULTS = false;
		boolean USE_SHELF_RENDER_LOGIC = false;
		if (!primary.isEmpty()) {
			UniJsonValue relatedSection = null;
			for (int i = primary.size() - 1; i >= 0; i--) {
				final boolean isShelfRenderer = primary.get(i).contains("shelfRenderer");
				final UniJsonValue f = primary.remove(i).getEither("itemSectionRenderer", "shelfRenderer");
				if (f.contains("sectionIdentifier") && f.getString("sectionIdentifier", "").equals("related-items")) {
					relatedSection = f;
					break;
				} else if (isShelfRenderer && f.contains("content") && f.get("content").contains("horizontalListRenderer")) {
					USE_SHELF_RENDER_LOGIC = true;
					relatedSection = f;
					break;
				} else
					continue;
			}
			primary.clear();
			if (relatedSection == null) {
				log.debug("Fellback to secondary results! [reason=RELATED_SECTION_WAS_NULL]");
				USE_SECONDARY_RESULTS = true;
			} else {
				processRelatedArray(ri, videoInfos, USE_SHELF_RENDER_LOGIC ? relatedSection.get("content").get("horizontalListRenderer").get("items").asArray()
						: relatedSection.get("contents").asArray());
			}
		} else {
			log.warn("Fellback to secondary results! [reason=PRIMARY_WAS_EMPTY]");
			USE_SECONDARY_RESULTS = true;
		}
		if (USE_SECONDARY_RESULTS) {
			final UniJsonValue s0 = results.get("secondaryResults");
			if (s0 == null || s0.isNull() || !s0.contains("secondaryResults")) {
				log.warn("Failed to find secondaryResults!");
				log.warn(results);
				return ri;
			}
			final UniJsonValue s1 = s0.get("secondaryResults");
			if (s1 == null || s1.isNull()) {
				log.warn("Failed to find results in secondaryResults!");
				log.warn(s0);
				return ri;
			}
			final UniJsonIterableValue s = s1.get("results").asArray();
//    		if (s.length() < 5 && !s.isEmpty()) {
//    			log.warn("[RelatedParser::Quirk(LOW_VIDEO_RELATED_COUNT)] Printing raw data as something isn't right.");
//    			log.dwarn(o.get("contents"));
//    		}
			processRelatedArray(ri, videoInfos, s);
		}
		if (results.contains("autoplay")) {
			final UniJsonValue autoPlay = results.get("autoplay").get("autoplay");
			if (autoPlay.contains("sets")) {
				final UniJsonValue set = autoPlay.get("sets").asArray().first();
				if (set.contains("autoplayVideo")) {
					final UniJsonValue autoplayVideo = set.get("autoplayVideo");
					final String videoId = autoplayVideo.get("watchEndpoint").getString("videoId");
					for (final VideoInfo vi : videoInfos) {
						if (vi.getID().equals(videoId) && !vi.isUpnext()) {
							vi.setUpnext(true);
							break;
						}
					}
				} else if (set.contains("mode") && set.contains("previousButtonRenderer")) {
					/*
					 * I'm going to assume that mode=NORMAL.
					 * 
					 * Also great idea YouTube, hurr durr you've ended the playlist how about you
					 * just go back!!!111111
					 */
					final UniJsonValue previousButtonRenderer = set.get("previousButtonRenderer");
					final String videoId = previousButtonRenderer.get("watchEndpoint").getString("videoId");
					// XXX: previousButtonRenderer also contains a index and playlistId object!
					for (final VideoInfo vi : videoInfos) {
						if (vi.getID().equals(videoId) && !vi.isUpnext()) {
							vi.setUpnext(true);
							break;
						}
					}
				} else {
					log.warn("Unknown autoplay set: " + set);
				}
			} else {
				log.warn("No sets found in autoplay: " + autoPlay);
			}
		}
		return ri;
	}

	public static RelatedInfo parseArray(final UniJsonValue relatedObj) {
		Variables.requireNonNull(relatedObj, "relatedObj");
		final List<VideoInfo> videoInfos = FastLists.list(20);
		final RelatedInfo ri = new RelatedInfo(videoInfos);
		processRelatedArray(ri, videoInfos, relatedObj.asArray());
		return ri;
	}
}
